package com.sunmouse.toolkit.widget.menu;

import android.content.Context;
import android.content.res.TypedArray;
import android.os.SystemClock;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;

import com.sunmouse.toollib.utils.LogUtils;
import com.sunmouse.toolkit.R;
import com.sunmouse.toolkit.widget.DragHelper;

/**
 * Created by Administrator on 2017/8/16.
 */
public class DragLayout extends ViewGroup implements DrawerLayoutImpl {
    private ViewDragHelper mDragHelper;
    private MenuDragCallback menuDragCallback;
    private View menuLayout;
    private View contentLayout;
    private LayoutParams mLp;
    private OnStatusChangedListener mOnStatusChangedListener;

    public void setOnStatusChangedListener(OnStatusChangedListener mOnStatusChangedListener) {
        this.mOnStatusChangedListener = mOnStatusChangedListener;
    }

    private final static int MIN_DRAG_MARGIN = 64;
    private final static int MIN_VELOCITY = 400;

    private int mDragMargin;

    private float minVelocity;

    private boolean mChildrenCanceledTouch;
    private boolean hasPeeked;

    private float offset;
    private float ds;

    static final int[] LAYOUT_ATTRS = new int[]{
            android.R.attr.layout_gravity
    };


    public DragLayout(Context context) {
        this(context, null);
    }

    public DragLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public DragLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        DisplayMetrics mMetrics = getResources().getDisplayMetrics();
        float density = mMetrics.density;
        TypedArray mArray = context.obtainStyledAttributes(attrs, R.styleable.DragLayout);
        mDragMargin = mArray.getDimensionPixelSize(R.styleable.DragLayout_dlDragMargin, (int) (MIN_DRAG_MARGIN * density + 0.5f));
//        mShadow=mArray.getColor(R.styleable.DragLayout_dlShadow,Color.RED);
        mArray.recycle();
//        setBackgroundColor(mShadow);

//        setLayoutParams(new ViewGroup.LayoutParams(mMetrics.widthPixels,mMetrics.heightPixels));
//        LogUtils.v("----w=" + mMetrics.widthPixels + "  h=" + mMetrics.heightPixels);
        mLp = new LayoutParams(mMetrics.widthPixels - mDragMargin, ViewGroup.LayoutParams.MATCH_PARENT);
        mLp.gravity = Gravity.LEFT;
        minVelocity = MIN_VELOCITY * density;

        menuDragCallback = new MenuDragCallback();
        mDragHelper = ViewDragHelper.create(this, 1.0f, menuDragCallback);
        mDragHelper.setEdgeTrackingEnabled(DragHelper.EDGE_LEFT);
        menuDragCallback.setDrag(mDragHelper);
        mDragHelper.setMinVelocity(minVelocity);

        setFocusableInTouchMode(true);

    }

    @Override
    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    }

    @Override
    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof LayoutParams
                ? new LayoutParams((LayoutParams) p)
                : p instanceof ViewGroup.MarginLayoutParams
                ? new LayoutParams((MarginLayoutParams) p)
                : new LayoutParams(p);
    }

    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof LayoutParams && super.checkLayoutParams(p);
    }

    @Override
    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(), attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
//        LogUtils.d("----------changed=" + changed);
        if (!changed) return;
        int w = getWidth();
        int h = getHeight();
        checkChildValid();
//        LogUtils.v("--menu:w=" + menuLayout.getMeasuredWidth() + " h=" + menuLayout.getMeasuredHeight());
        int menuWidth = menuLayout.getMeasuredWidth();
        if (menuWidth > w - mDragMargin) menuLayout.setLayoutParams(mLp);
        menuLayout.layout((-w + mDragMargin), 0, 0, h);
        ViewCompat.setScaleY(menuLayout, 0.5f);
        ViewCompat.setScaleX(menuLayout, 0.5f);
        contentLayout.layout(0, 0, w, h);
//        LogUtils.d("---onLayout---------mw=" + menuLayout.getWidth());
    }

    /**
     * set menu view and content view
     */
    private void checkChildValid() {
        int count = getChildCount();
        if (count != 2) {
            throw new RuntimeException("child count is not equal to 2");
        }
        for (int i = 0; i < count; i++) {
            LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
            if (lp.gravity == Gravity.LEFT) {
                menuLayout = getChildAt(i);
                contentLayout = getChildAt(i == 0 ? 1 : 0);
                return;
            }
        }
        throw new RuntimeException("one of children should be set \"android:layout_gravity=\"left\"\"");
    }


    @Override
    public void computeScroll() {
//        LogUtils.v("---------------------------computeScroll");
        if (mDragHelper.continueSettling(true))
            ViewCompat.postInvalidateOnAnimation(this);
//        super.computeScroll();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mDragHelper.processTouchEvent(event);
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mChildrenCanceledTouch = false;
                break;
            case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                closeDrawer();
                break;
        }
        return true;
    }

    /**
     * close drawer
     */
    private void closeDrawer() {
        LogUtils.d("---------------closeDrawer");
        boolean invalidateFlag = false;
        if (hasPeeked) {
            hasPeeked = false;
            int left = -menuLayout.getWidth();
            int top = menuLayout.getTop();
            invalidateFlag |= mDragHelper.smoothSlideViewTo(menuLayout, left, top);
        }
        menuDragCallback.removeCallbacks();//remove cks
        if (invalidateFlag)
            invalidate();
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean mDragIntercept = mDragHelper.shouldInterceptTouchEvent(ev);

        switch (ev.getAction()) {
            case MotionEvent.ACTION_MOVE:
                if (mDragHelper.checkTouchSlop(DragHelper.DIRECTION_ALL)) {
                    menuDragCallback.removeCallbacks();
                }
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                closeDrawer();
                break;
        }
//        mDragHelper.shouldInterceptTouchEvent(ev);
        return mDragIntercept || hasPeeked;
    }

    @Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
        super.requestDisallowInterceptTouchEvent(disallowIntercept);
        if (disallowIntercept) {
            closeDrawer();
        }
    }

    @Override
    public void setChildInsets(Object insets, boolean drawStatusBar) {

    }

    /**
     * drag callback
     */
    private class MenuDragCallback extends ViewDragHelper.Callback {

        private ViewDragHelper mDrag;

        public void setDrag(ViewDragHelper mDrag) {
            this.mDrag = mDrag;
        }

        /**
         * remove ck
         */
        private void removeCallbacks() {
            DragLayout.this.removeCallbacks(mPeekRunnable);
        }

        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return child == menuLayout;
        }

        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            int childWidth = changedView.getWidth();
            if (changedView == menuLayout) {
                offset = (float) (childWidth + left) / childWidth;
            } else {
                offset = (float) (getWidth() - left) / childWidth;
            }
            changedView.setVisibility(offset == 0 ? INVISIBLE : VISIBLE);

            invalidate();
            /**
             * critical point :
             * >left=child.width:
             * >left=0:
             */
            resetDirt(changedView, left, dx);
            LogUtils.d("----------------dx=" + dx + "---menuLeft=" + changedView.getLeft());
//            ViewCompat.offsetLeftAndRight(contentLayout, (int) (dx * (1 - (float) mDragMargin / getWidth())));
//            ViewCompat.setScaleY(changedView, 1 + ds * 2);
//            ViewCompat.setScaleX(changedView, 1 + ds * 2);
            ViewCompat.offsetLeftAndRight(contentLayout, dx);
            ViewCompat.setScaleY(changedView, 0.5f + ds);
            ViewCompat.setScaleX(changedView, 0.5f + ds);
            ViewCompat.setScaleY(contentLayout, 1 - ds / 2);
            ViewCompat.setScaleX(contentLayout, 1 - ds / 2);
        }

        /**
         * reset horizontal translations
         *
         * @param dx
         */
        private void resetDirtX(int dx) {


        }

        /**
         * reset dirt distance scale
         *
         * @param v
         * @param left
         * @param dx
         */
        private void resetDirt(View v, int left, int dx) {
            int childWidth = v.getWidth();
            if (-left == childWidth) {
                ds = 0.0f;
//                mDirtX
            } else if (left == 0) {
                ds = 0.5f;
            } else {
                ds += (float) dx / childWidth / 2;
            }
        }

        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            super.onViewReleased(releasedChild, xvel, yvel);
            int left = 0;
            if (releasedChild == menuLayout) {
                left = (xvel > 0.0f || xvel == 0.0f && offset > 0.5f) ? 0 : -releasedChild.getWidth();
                if (mOnStatusChangedListener != null)
                    mOnStatusChangedListener.onStatusChanged(left == 0);
                LogUtils.v("-------------offset=" + offset + ":left=" + left + ":mw=" + releasedChild.getWidth());
            } else {
                final int width = getWidth();
                left = xvel < 0 || xvel == 0 && offset > 0.5f ? width - releasedChild.getWidth() : width;
            }
            mDrag.settleCapturedViewAt(left, releasedChild.getTop());
            invalidate();


        }

        @Override
        public int getViewHorizontalDragRange(View child) {
//            LogUtils.d("dragRange--" + (child == menuLayout) + "--" + child.getWidth());
            return menuLayout == child ? child.getWidth() : 0;
        }

        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            int clamp;
            if (child == menuLayout)
                clamp = Math.max(-child.getWidth(), Math.min(left, 0));
//                clamp = Math.max(-child.getWidth()+mDragMargin, Math.min(left, 0));
            else
                clamp = Math.max(getWidth() - child.getWidth(), Math.min(left, getWidth()));
            LogUtils.d("---------------clamp=" + clamp);
            return clamp;
        }


        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            return child.getTop();
        }

        @Override
        public void onEdgeTouched(int edgeFlags, int pointerId) {
            postDelayed(mPeekRunnable, 160);
        }

        @Override
        public void onEdgeDragStarted(int edgeFlags, int pointerId) {
            mDrag.captureChildView(menuLayout, pointerId);
            hasPeeked = false;
        }

        private final Runnable mPeekRunnable = new Runnable() {
            @Override
            public void run() {
                peekDrawer();
            }
        };

        private void peekDrawer() {
//            LogUtils.d("---------------peekDrawer");
            int dL = -menuLayout.getWidth() + mDrag.getEdgeSize();
            int dT = menuLayout.getTop();
            mDrag.smoothSlideViewTo(menuLayout, dL, dT);
            hasPeeked = true;
            invalidate();

            cancelChildViewTouch();
        }


    }

    void cancelChildViewTouch() {
        // Cancel child touches
//        LogUtils.d("------------cancelChildViewTouch");
        if (!mChildrenCanceledTouch) {
            final long now = SystemClock.uptimeMillis();
            final MotionEvent cancelEvent = MotionEvent.obtain(now, now,
                    MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
            final int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                getChildAt(i).dispatchTouchEvent(cancelEvent);
            }
            cancelEvent.recycle();
            mChildrenCanceledTouch = true;
//            LogUtils.d("------------cancelChild");
        }
    }


    /**
     * generate default layout param;
     */
//    private LayoutParams generateLayoutParams(int index) {
//        LayoutParams lp;
//        switch (index) {
//            default:
//            case 0:
//                lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
//                break;
//            case 1:
//                lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
//                break;
//            case 2:
//                lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
//                break;
//        }
//        return lp;
//    }


    static class LayoutParams extends ViewGroup.MarginLayoutParams {
        private static final int FLAG_IS_OPENED = 0x1;
        private static final int FLAG_IS_OPENING = 0x2;
        private static final int FLAG_IS_CLOSING = 0x4;

        public int gravity = Gravity.NO_GRAVITY;
        float onScreen;
        boolean isPeeking;
        int openState;

        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);

            final TypedArray a = c.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
            this.gravity = a.getInt(0, Gravity.NO_GRAVITY);
            a.recycle();
        }

        public LayoutParams(int width, int height) {
            super(width, height);
        }

        public LayoutParams(int width, int height, int gravity) {
            this(width, height);
            this.gravity = gravity;
        }

        public LayoutParams(LayoutParams source) {
            super(source);
            this.gravity = source.gravity;
        }

        public LayoutParams(ViewGroup.LayoutParams source) {
            super(source);
        }

        public LayoutParams(ViewGroup.MarginLayoutParams source) {
            super(source);
        }
    }


    /**
     * opened or closed
     */
    public interface OnStatusChangedListener {
        /**
         * isOpened= true : the drawer is opened
         * isOpened=false: the drawer is closed
         *
         * @param isOpened
         */
        void onStatusChanged(boolean isOpened);
    }


}
